反汇编代码分析
text::masm32汇编
text::逆向工具
一、函数分析
1 系统函数
系统函数是库中自带的函数,相关程序如OD可以将其直接分析出来。
下面仅举出一些例子,实际系统函数有很多。
系统函数关闭优化:优化——启动内部函数——否。
MessageBoxA(); MessageBoxW();
printf();
sokcet();
|
;strcmp(a,b) ;取a,b前四位的地址 mov edx,dword ptr [esp+4] mov ecx,dword ptr [esp+8] ;检测最后两位是否为00,保证4字节对齐 test edx,00000003 jnz alignment start: ;比较第一位 mov eax,dword ptr [edx] cmp al,byte ptr [ecx] jne false ;判断是否是'\0' or al,al jz end ;比较第二位 cmp ah,byte ptr [ecx+1] jne false or ah,ah jz end ;右移位,继续比较 shr eax,10 cmp al,byte ptr [ecx+3] jne false or al,al jz end cmp ah,byte ptr [ecx+4] jne false or ah,ah jz end ;比较接下来四个字符 add ecx,4 add,edx,4 ;判断结尾,没有结尾继续回到上面重复比较 or ah,ah jnz start
|
2 用户函数
用户自己编写的函数。
call会入栈EIP一次,retn会出栈一次,所以一段栈中的存储大致如下:
【栈顶】局部变量 | 上一函数EBP | 上一函数的EIP 【栈底】
所以一般而言:
[EBP - ?? ]:本函数的局部变量
[EBP + ??]:上一函数的局部变量
;空函数必做的事 void fun(){} push ebp ;保存上一函数的栈底,同时esp会增加,esp此时指向了当前函数的栈底。 mov ebp,esp ;把esp给ebp,让ebp做栈底,之后操作改变的偶数esp的值。 pop ebp ;出栈,获得上一函数栈底,esp此时指向的应该是上一函数栈顶。 retn
;分配空间 void fun(){int a=3,b=5; char c=1;} push ebp mov ebp,esp sub esp,0C ;局部变量存在栈,开辟空间,会4对齐,所以是0x0C mov dword ptr [local.1],3 mov dword ptr [local.2],5 mov byte ptr [lcoal.3+3],1 mov esp,ebp ;复原基址,返回上一函数 pop ebp ret
;参数传递 ;void main(){ printf(add(3,6)); } ;int add(int a,int b){ return a+b; } ;主函数 push 6 ;传参,逆向入栈,便于正向出栈 push 3 call add add esp,8 ;恢复栈,保持栈平衡 ;add函数 push ebp mov ebp,esp mov eax,dword ptr [ebp+8] mov eax,dword ptr [ebp+0C] push ebp ret
|
3 调用约定
VC:配置属性——C/C++——高级——调用约定
调用约定可以写在函数前,如 int __cdecl add(){}
C中不加说明默认__cdecl
。
C++也一样,但是默认调用方式可以在IDE(开发环境)中修改。
带有可变参数的函数必须用__cdecl
方式。如:int printf(char* fmtstr,...);
调用约定分类:
- cdecl:
__cdecl
是C Declaration的缩写,所以参数是从右到左依次入栈,并且参数是由调用者清除,称为手动清栈。(注:不是指程序员清栈)
stdcall:
- API 调用是 standardcall,是C++的标准调用方式,所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是 this 指针。
- 这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是 retn X,X表示参数占用的字节数(内存空间大小)。CPU在ret之后自动弹出X个字节的栈空间,称为自动清栈。
fastcall:
__fastcall
是编译器指定的快速调用方式。
- fastcall通常规定将前两个(或若干个)参数由寄存器传递,其余参数还是通过堆栈传递。
- 不同编译器编译的程序规定的寄存器不同,返回方式和stdcall相同。
; 1.cdecl
;示例代码int add(int a,int b){ return a+b; } ;调用代码 push ebp mov ebp,esp mov eax,dword ptr [arg.1] mov eax,dword ptr [arg.2] pop ebp ret ;主函数 push [arg.2] ;参数从右往左入栈 push [arg.1] call func add esp,8 ;调用者清栈
|
; 2.stdcall
;示例代码 ;调用代码 push ebp mov ebp,esp mov eax,dword ptr [arg.1] mov eax,dword ptr [arg.2] pop ebp ret 8 ;自动清栈 ;主函数 push [arg.2] ;参数从右往左入栈 push [arg.1] call func
|
; 3.fastcall
;示例代码(假设有三个参数) ;调用代码 push ebp mov ebp,esp sub esp,8 mov dword ptr [local.2],edx mov dword ptr [local.1],ecx mov eax,dword ptr [local.1] add eax,dword ptr [local.2] add eax,dword ptr [arg.3] pop ebp ret 8 ;自动清栈 ;主函数 push [arg.3] mov edx,push [arg.2] ;参数从右往左入栈 mov ecx,push [arg.1] call func
|
二、变量分析
1 指针
;定义int *p = &i; lea edx,[local.2] ;edx = &i mov dword ptr [local.1],edx ;p = edx;
;指针操作 mov ebx,p add word ptr [ebx],0x333 ;i+=0x333(此处前面要加上单元大小,否则默认byte)
|
2 i++和++i
int i = 0; ;int j = i++(先赋值再加) mov dword ptr [local.2],0 ;j = 0 mov ecx,dword ptr [local.1] ;ecx = i mov dword ptr [local.2],ecx ;j = ecx mov edx,dword ptr [local.1] ;edx = i add edx,1 ; edx = edx + 1 mov dword ptr [local.1],edx ;i = edx
;int j = ++i(先加再赋值) mov ecx,dword ptr [local.1] ;ecx = i add ecx,1 ; ecx = ecx + i mov dword ptr [local.1],ecx ;i = ecx mov edx,dword ptr [local.1] ;edx = i mov dword ptr [local.2],edx ;j = edx
;完全优化:int i=0;int j=i++;printf("%d",j);j=++i;printf("%d",j); ;编译器会直接先输出0,然后i += 2,然后输出2。
|
3 浮点数
st0-st7(MMX,FPU):80位两用寄存器器。
;老汇编 fld fstp fadd
;新汇编 ;float fl = 8.765f; movss xmm0,dword ptr [0A32118h] movss dword ptr [fl],xmm0 ;fl++; movss xmm0,dword ptr [fl] addss xmm0,dword ptr [0A32114h] movss dword ptr [fl],xmm0
;时间优化 movups xmm0,dqword ptr ds:[0x2F2118] ;将内存地址0x2F2118处的8字节数据(即一个双精度浮点数)加载到XMM0寄存器中。movups表示以128位为单位进行加载,因为XMM0寄存器是128位的。 movsd qword ptr ss:[esp],xmm0 ;将XMM0寄存器中的数据转存到栈顶(ESP寄存器指向的位置)中。这里使用了movsd指令,表示以64位为单位进行转存。
|
三、结构分析
1 if-else
int main() { int a = 3,b = 2; if(a > b) { printf("a > b\n"); } else { printf("a <= b\n"); } return 0; }
|
2 switch-else
2.1 普通形式
int main() { printf("switch-case\n"); int a = 3; switch(a) { case 1: printf("1\n"); break; case 2: printf("2\n"); break; case 3: printf("3\n"); break; default: printf("default\n"); break; } printf("end\n"); }
mov eax,dword ptr [ebp-4] mov dword ptr [ebp-8],eax
cmp dword ptr [ebp-8],1 je addr cmp dword ptr [ebp-8],2 je addr cmp dword ptr [ebp-8],3 je addr jmp addr
call printf(1) jmp addr call printf(2) jmp addr call printf(3) jmp addr printf("default")
|
2.2 跳转表
int main() { printf("switch-case\n"); int a = 0x20; switch(a) { case 0x13:printf("0x13\n");break; case 0x15:printf("0x15\n");break; case 0x10:printf("0x10\n");break; case 0x20:printf("0x20\n");break; case 0x22:printf("0x22\n");break; default:printf("default");break; } printf("end\n"); }
|
00341080 PUSH EBP 00341081 MOV EBP,ESP 00341083 SUB ESP,8 00341086 PUSH 汇编语言.00342100 ; /Arg1 = 00342100 ASCII "switch-case" 0034108B CALL 汇编语言.00341040 ; \汇编语言.00341040 00341090 ADD ESP,4
00341093 MOV DWORD PTR SS:[EBP-8],20 0034109A MOV EAX,DWORD PTR SS:[EBP-8] 0034109D MOV DWORD PTR SS:[EBP-4],EAX 003410A0 MOV ECX,DWORD PTR SS:[EBP-4]
003410A3 SUB ECX,10 ; a-10,就是a减去case中的最小值 003410A6 MOV DWORD PTR SS:[EBP-4],ECX 003410A9 CMP DWORD PTR SS:[EBP-4],12 ;因为case中最大为0x22,最小0x10,所以范围是12。如果a-10不落在0-12中表示不是这里的面的值,直接下面跳转结束,否则会有一个跳转表,计算出的值会对应跳转到一个指定的地址,输出指定的值。 003410AD JA SHORT 汇编语言.0034110B
003410AF MOV EDX,DWORD PTR SS:[EBP-4] ;落在范围内则找表跳转到指定位置 003410B2 MOVZX EAX,BYTE PTR DS:[EDX+341144] 003410B9 JMP DWORD PTR DS:[EAX*4+34112C]
003410C0 PUSH 汇编语言.00342110 ; /Arg1 = 00342110 ASCII "0x13" 003410C5 CALL 汇编语言.00341040 ; \汇编语言.00341040 003410CA ADD ESP,4 003410CD JMP SHORT 汇编语言.00341118
003410CF PUSH 汇编语言.00342118 ; /Arg1 = 00342118 ASCII "0x15" 003410D4 CALL 汇编语言.00341040 ; \汇编语言.00341040 003410D9 ADD ESP,4 003410DC JMP SHORT 汇编语言.00341118
003410DE PUSH 汇编语言.00342120 ; /Arg1 = 00342120 ASCII "0x10" 003410E3 CALL 汇编语言.00341040 ; \汇编语言.00341040 003410E8 ADD ESP,4 003410EB JMP SHORT 汇编语言.00341118
003410ED PUSH 汇编语言.00342128 ; /Arg1 = 00342128 ASCII "0x20" 003410F2 CALL 汇编语言.00341040 ; \汇编语言.00341040 003410F7 ADD ESP,4 003410FA JMP SHORT 汇编语言.00341118
003410FC PUSH 汇编语言.00342130 ; /Arg1 = 00342130 ASCII "0x22" 00341101 CALL 汇编语言.00341040 ; \汇编语言.00341040 00341106 ADD ESP,4 00341109 JMP SHORT 汇编语言.00341118
0034110B PUSH 汇编语言.00342138 ; /Arg1 = 00342138 ASCII "default" 00341110 CALL 汇编语言.00341040 ; \汇编语言.00341040 00341115 ADD ESP,4
00341118 PUSH 汇编语言.00342140 ; /Arg1 = 00342140 ASCII "end" 0034111D CALL 汇编语言.00341040 ; \汇编语言.00341040 00341122 ADD ESP,4
00341125 XOR EAX,EAX 00341127 MOV ESP,EBP 00341129 POP EBP 0034112A RETN
|
3 for
for (int i = 1; i <= 10; ++i) { printf("%d", i); }
;优化/Od(无优化) push ecx ;入栈ecx,不需要关心ecx的值,只是开辟栈存i而已 00591091 MOV DWORD PTR SS:[EBP-4],1 ;赋初值 00591098 JMP SHORT 汇编语言.005910A3 ;进入循环 ;循环 0059109A MOV EAX,DWORD PTR SS:[EBP-4] ;++i 0059109D ADD EAX,1 005910A0 MOV DWORD PTR SS:[EBP-4],EAX 005910A3 CMP DWORD PTR SS:[EBP-4],0A ;i>10结束 005910A7 JG SHORT 汇编语言.005910BC 005910A9 MOV ECX,DWORD PTR SS:[EBP-4] 005910AC PUSH ECX ;Arg2 005910AD PUSH 汇编语言.00592108 ;Arg1 = 00592108 ASCII "%d" 005910B2 CALL 汇编语言.00591040 ;printf("%d",i); 005910B7 ADD ESP,8 005910BA JMP SHORT 汇编语言.0059109A
;优化/O1(大小优化,指令变少) ;可能还会有 mov edi, dword ptr [<&MSUCR100printf>],call edi的指令,这也是一种优化 00311034 PUSH ESI ;保护esi 0031103F XOR ESI,ESI ;esi=0 00311042 INC ESI ;先i=1 ;循环 00311043 PUSH ESI ;Arg2 00311044 PUSH 汇编语言.00312108 ;Arg1,ASCII "%d" 00311049 CALL 汇编语言.00311006 0031104E INC ESI 0031104F POP ECX ;两次pop相当于上面的ADD ESP,8,保证堆栈平衡 00311050 POP ECX 00311051 CMP ESI,0A 00311054 JLE SHORT 汇编语言.00311043 ;i<=10继续循环 ;循环结束 00311056 POP ESI ;取出esi
;优化/O2(速度优化) 00851040 PUSH ESI 0085104E MOV ESI,1 ;i=1 ;循环开始 00851053 PUSH ESI ; Arg2 00851054 PUSH 汇编语言.00852108 ; Arg1 = 00852108 ASCII "%d" 00851059 CALL 汇编语言.00851010 ; 汇编语言.00851010 0085105E INC ESI 0085105F ADD ESP,8 00851062 CMP ESI,0A 00851065 JLE SHORT 汇编语言.00851053 ;循环结束 00851067 POP ESI
;优化/Ox(完全优化) ;该实例代码优化结果基本同/O2
|
4 while/do-while
; while(i<=10){} loop: cmp dowrd ptr [local1],0A jg end statement jmp loop end: ...
; do{}while(i<=10); loop: statement cmp dowrd ptr [local.1],0A jle loop
|
5 逻辑运算
;a || b cmp dword ptr [a],0 jne true cmp dwrod ptr [b],0 jne true
;a && b cmp dword ptr [a],0 je false cmp dwrod ptr [b],0 je false
;b = !a cmp dword ptr [a],0 sete al mov dword ptr [b],eax
;b = ~a mov ecx,dword ptr [a] not ecx mov dword ptr [b],ecx
;c = a ^ b movzx eax,byte ptr [b] movzx ecx,byte ptr [a] xor eax,ecx mov byte ptr [c],al
|
四、汇编指令
1 repne和scasb
常用于:
- 寻找字符串中字符,字符存在al中。
- 计算字符串长度。
- 在内存中地位一串特征码。
;scasb,编译后如下:(相当于cmp byte ptr[edi],al。同时还会根据DF的值对edi加n,取决于操作的内存大小) scas byte ptr [edi] ;同理两个 scasw; scas word ptr [edi] scasd; scas dword ptr [edi]
;repnz/repne scasb,编译后如下:(当ecx!=0且ZF=0,重复后面的指令(scas byte ptr es:[edi]),且每次ecx--) repne scas byte ptr es:[edi]
|
2 repe和cmpsb
常用于比较字符串是否相等
;同理上面 ;cmpsb,cmpsw,cmpsd cmps byte ptr [edi],byte ptr [esi] cmps word ptr [edi],byte ptr [esi] cmps dword ptr [edi],byte ptr [esi]
;repe/repz(当ecx!=0且ZF=1重复后面语句并ecx--) repe cmpsb
|
;汇编编写字符串比较函数 ;__declspec(naked)告诉编译器该函数是纯汇编实现,不自动添加寄存器保护和堆栈平衡 ; 窄字节 __declspec(naked) int asm_strcmp(char *s1,char* s2) { __asm { push ebp mov ebp,esp ;构建新栈底 push ecx push edi push esi push edx xor al,al mov edi,[ebp+4+4] ;s1 mov ecx,-1 CLD repnz scasb not ecx mov edi,[ebp+4+4] ;s1 mov esi,[ebp+4+8] ;s2 repz cmpsb xor eax,eax xor edx,edx mov al,[edi-1] mov dl,[esi-1] sub eax,edx pop edx pop esi pop edi pop ecx pop ebp retn } }
; 宽字节 __declspec(naked) int asm_strcmp(wchar_t *s1,wchar_t* s2) { __asm { push ebp mov ebp,esp ;构建新栈底 push ecx push edi push esi push edx xor ax,ax ;区别窄字节 mov edi,[ebp+4+4] ;s1 mov ecx,-1 CLD repnz scasw ;区别窄字节 not ecx mov edi,[ebp+4+4] ;s1 mov esi,[ebp+4+8] ;s2 repz cmpsw ;区别窄字节 xor eax,eax xor edx,edx mov al,[edi-2] ;区别窄字节 mov dl,[esi-2] ;区别窄字节 sub eax,edx pop edx pop esi pop edi pop ecx pop ebp retn } }
|
3 rep和串操作
;串存储 ;stosb,stosw,stosd | stos byte ptr [edi] move byte ptr [edi],al move word ptr [edi],ax move dword ptr [edi],eax
;rep sto byte ptr [edi](ecx计数,edi存值,每次存完edi += n) rep stosb
;串载入(只能载入一段,eax中的值会被一直覆盖) ;lodsb,lodsw,lodsd | lods byte ptr [edi] mov al, byte ptr [esi] mov ax, word ptr [esi] mov eax, dword ptr [esi] ;rep lods byte ptr [edi](ecx计数,edi存值,每次存完edi += n) rep lodsb
|
; 自定义移动字符串,效率远高于memset int src[10]; __asm { mov ecx,len(src) xor eax,eax lea edi,src rep stosd }
|
五、可能的花指令
对程序没有实际影响,却会干扰破解者进行破解。
;不带进位循环左移32位等于没移 rol i,0x20
|
六、壳